package archer.framework.security.token.impl;

import archer.framework.security.token.Token;
import jodd.util.HashCode;

import java.io.Serializable;
import java.util.*;

/**
 * @author christ
 * @date 2016/5/9
 */
public class DefaultToken implements Token, Serializable {

    private String id;
    private Date startTimestamp;
    private Date invalidateTimestamp;
    private Date lastAccessTime;
    private long timeout;
    private boolean expired;

    private Map<Object, Object> attributes;

    public DefaultToken() {
        this.startTimestamp = new Date();
        this.lastAccessTime = this.startTimestamp;
    }

    //--------------------------apis-------------------------------

    /**
     * token是否可用
     *
     * @return
     */
    @Override
    public boolean isValidate() {
        if (isInvalidate()) {
            return false;
        }

        //check for expiration
        if (isTimedOut()) {
            expire();
            return false;
        }
        return true;
    }

    @Override
    public void touch() {
        this.lastAccessTime = new Date();
    }

    @Override
    public void invalidate() {
        if (this.invalidateTimestamp == null) {
            this.invalidateTimestamp = new Date();
        }
    }

    @Override
    public Collection<Object> getAttributeKeys() {
        Map<Object, Object> attributes = getAttributes();
        if (attributes == null) {
            return Collections.emptySet();
        }
        return attributes.keySet();
    }

    @Override
    public Object getAttribute(Object key) {
        Map<Object, Object> attributes = getAttributes();
        if (attributes == null) {
            return null;
        }
        return attributes.get(key);
    }

    @Override
    public void setAttribute(Object key, Object value) {
        if (value == null) {
            removeAttribute(key);
        } else {
            getAttributesLazy().put(key, value);
        }
    }

    @Override
    public Object removeAttribute(Object key) {
        Map<Object, Object> attributes = getAttributes();
        if (attributes == null) {
            return null;
        } else {
            return attributes.remove(key);
        }
    }

    @Override
    public long getTimeout() {
        return timeout;
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    @Override
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public Date getStartTimestamp() {
        return startTimestamp;
    }

    public void setStartTimestamp(Date startTimestamp) {
        this.startTimestamp = startTimestamp;
    }

    @Override
    public Date getLastAccessTime() {
        return lastAccessTime;
    }

    public void setLastAccessTime(Date lastAccessTime) {
        this.lastAccessTime = lastAccessTime;
    }

    //------------------------inner---------------------

    protected Map<Object, Object> getAttributes() {
        return attributes;
    }

    protected DefaultToken setAttributes(Map<Object, Object> attributes) {
        this.attributes = attributes;
        return this;
    }

    protected boolean isExpired() {
        return expired;
    }

    protected boolean isInvalidate() {
        return getInvalidateTimestamp() != null;
    }

    protected void expire() {
        invalidate();
        this.expired = true;
    }

    protected void setExpired(boolean expired) {
        this.expired = expired;
    }

    protected Date getInvalidateTimestamp() {
        return invalidateTimestamp;
    }

    protected void setInvalidateTimestamp(Date invalidateTimestamp) {
        this.invalidateTimestamp = invalidateTimestamp;
    }

    protected boolean isTimedOut() {

        if (isExpired()) {
            return true;
        }

        long timeout = getTimeout();

        if (timeout >= 0l) {

            Date lastAccessTime = getLastAccessTime();
            long expireTimeMillis = System.currentTimeMillis() - timeout;
            Date expireTime = new Date(expireTimeMillis);
            return lastAccessTime.before(expireTime);
        }

        return false;
    }

    private Map<Object, Object> getAttributesLazy() {
        Map<Object, Object> attributes = getAttributes();
        if (attributes == null) {
            attributes = new HashMap<Object, Object>();
            setAttributes(attributes);
        }
        return attributes;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getName()).append(",id=").append(getId());
        return sb.toString();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof DefaultToken) {
            DefaultToken other = (DefaultToken) obj;
            String thisId = getId();
            String otherId = other.getId();
            if (thisId != null && otherId != null) {
                return thisId.equals(otherId);
            }
        }
        return false;
    }

    @Override
    public int hashCode() {
        return HashCode.hash(HashCode.SEED, this);
    }
}
